#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
命令行模拟
author: yooongchun@foxmail.com
"""
import sys
import time
import argparse
import threading
import pathlib

from rich import print

import negamax
import evaluate
import chessboard
from util import Point, Score


def trans(v):
    """符号转换"""
    if v == 1:
        return "x"
    if v == -1:
        return "o"
    return "-"


def save(board: chessboard.ChessBoard, name:str=""):
    """save history board"""
    name = f"examples/{name}-{int(time.time()*1000)}.json"
    path = pathlib.Path(__file__).absolute().parent.parent / name
    print(f"Save chess board to {path}")
    board.save(str(path))


def load(board: chessboard.ChessBoard, name: str="chess.json"):
    """load board"""
    name = f"examples/{name}-{int(time.time()*1000)}.json"
    path = pathlib.Path(__file__).absolute().parent.parent / name
    print(f"Load chess history from {path}")
    board.load(str(path))


def print_inplace(board:chessboard.ChessBoard, tip="", require_input:bool=False, init=False):
    """控制台原位更新"""
    mat = board.get_board()
    x = y = -1
    if board.last:
        x, y = board.last.row, board.last.col
    # 回退到上一行，所以有几个\n就要加几行
    if not init: # 初始时不回退
        for _ in range(len(mat)+2):
            sys.stdout.write(u'\u001b[1A')
    # 第一行序号
    text = "  " + "  ".join(hex(i).upper().replace("0X", "") for i in range(len(mat)))
    sys.stdout.write(u'\u001b[1000D\u001b[2K' + text + '\n')
    for i in range(len(mat)):
        if i == x and y != -1:
            text_curr = f' [{trans(mat[i][y])}] ' # 当前位置，需要着重显示
            text = "  ".join(trans(v) for v in mat[i][:y]) + text_curr + "  ".join(trans(v) for v in mat[i][y+1:])
        else:
            text = "  ".join(trans(v) for v in mat[i])
        text = hex(i).upper().replace("0X", "") + " " + text
        # '\u001b[1000D\u001b[2K'的作用是把这一行清空
        sys.stdout.write(u'\u001b[1000D\u001b[2K' + text + '\n')
    if not require_input:
        sys.stdout.write(u'\u001b[1000D\u001b[2K' + tip + '\n')
        var = ""
    else:
        var = input(u'\u001b[1000D\u001b[2K' + tip)
    sys.stdout.flush()
    return var


def ai_sync_think(board, max_depth, neighbor, res:list):
    """独立线程执行思考"""
    mms = negamax.MinMaxSearcher(board, neighbor)
    mms.search(max_depth)
    with threading.Lock():
        res.append(mms.best_move)


def parse_input(board: chessboard.ChessBoard, text: str):
    """Parse console input content"""
    # save|load|play 
    text = text.strip()
    if text.startswith("s"):
        action = "save"
    elif text.startswith("l"):
        action = "load"
    else:
         action = "play"
    if action == "save":
        filename = text.split(" ")[-1]
        save(board, filename)
        return action, filename
    elif action == "load":
        filename = text.split(" ")[-1]
        load(board, filename)
        return action, filename
    else:
        if len(text) == 2:
            x, y = eval(f"0x{text[0]}"), eval(f"0x{text[1]}")
            return action, (x, y, "ok")
        else:
            tip = "Only Need to Input the row/col Number,(eg:77,8a,cf)!"
            return action, (None, None, tip)


def is_game_over(board: chessboard.ChessBoard, turn: bool):
    """Game over?"""
    if board.is_empty():
        bscore = wscore = 0
    else:    
        e = evaluate.Evaluation(board, [board.get_history()[-1]])
        bscore = e.black_score()
        wscore = e.white_score()
    if not board.is_empty() and (bscore == Score.WIN or wscore == Score.WIN):
        tip = "AI win!" if not turn else "You win!"
        tip = tip + f" save board?(y/n): "
        ans = print_inplace(board, tip=tip, init=False, require_input=True)
        if ans.strip() == "y":
            save(board, f"{'AI' if not turn else 'human'}-win")
        return True
    if board.is_full():
        tip = "No empty position to go!"
        tip = tip + f" save board?(y/n): "
        ans = print_inplace(board, tip=tip, init=False, require_input=True)
        if ans.strip() == "y":
            save(board, "full-board")
        return True


def simulate(size: int, max_depth:int=2, neighbor:int=1, ai_first:bool=False):
    """模拟"""
    board = chessboard.ChessBoard(size=size)
    turn = ai_first
    init = True
    while True:
        if is_game_over(board, turn):
            break
        if turn: # AI play
            res = []
            threading.Thread(target=ai_sync_think, args=(board, max_depth, neighbor, res)).start()
            start = time.time()
            while not res:
                time.sleep(0.5)
                used = f'{time.time()-start:0.1f}s'
                print_inplace(board, tip=f"AI is thinking...{used}", init=init)
                init = False
            best_move = res[0]
            if best_move:
                board.go(best_move)
                turn = False
            else:
                tip = "No best move! You win!"
                tip = tip + f" save board?(y/n): "
                ans = print_inplace(board, tip=tip, init=init, require_input=True)
                if ans.strip() == "y":
                    save(board, "human-win")
                break
        else: # human play
            text = print_inplace(board, tip="Your turn: ", init=init, require_input=True)
            action, res = parse_input(board, text)
            if action in ["save", "load"]:
                print(f"{action} done! filename: {res}")
                break
            move = Point(res[0], res[1])
            status = res[0] and board.is_empty(move)
            if status:
                board.go(move)
                turn = True
            else:
                while not status:
                    tip = res[2] if res[2] != 'ok'else "Error! Position not empty!"
                    text = print_inplace(board, tip=f"{tip} Input again: ", init=False, require_input=True)
                    action, res = parse_input(board, text)
                    move = Point(res[0], res[1])
                    status = res[0] and board.is_empty(move)
                turn = True
                board.go(move)
        init = False


def parse_args():
    """解析命令行参数"""
    parser = argparse.ArgumentParser()
    parser.add_argument("-s", "--size", type=int, default=15, help="board size")
    parser.add_argument("-d", "--depth", type=int, default=4, help="AI search max depth")
    parser.add_argument("-n", "--neighbor", type=int, default=1, help="AI search max depth")
    parser.add_argument("--ai-first", action="store_true", default=False, help="AI first")
    args = parser.parse_args()
    return args


def main():
    """主函数入口"""
    args = parse_args()
    simulate(args.size, args.depth, args.neighbor, args.ai_first)


if __name__ == "__main__":
    main()
